package com.mr.security.web.commons.config;

import com.mr.security.web.commons.filter.ValidateCodeFilter;
import com.mr.security.web.commons.handler.CustomAuthenticationFailureHandle;
import com.mr.security.web.commons.handler.CustomAuthenticationSuccessHandle;
import com.mr.security.web.commons.utils.Constants;
import com.mr.security.web.webapp.rbac.service.UserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;

/**
 * Class: SpringSecurityConfig
 * User (作者):MRui
 * TODO (描述)：Security配置类
 * Date 2019-01-13 14:36
 */
@Configuration//配置
@EnableWebSecurity//web支持
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    //security属性配置文件
    @Autowired
    private SecurityPropertiesConfig securityProperties;
    //自定义身份验证成功处理
    @Autowired
    private CustomAuthenticationSuccessHandle successHandle;
    //自定义身份验证失败处理
    @Autowired
    private CustomAuthenticationFailureHandle failureHandle;
    //记住我功能数据库交互
    @Autowired
    private DataSource dataSource;
    //用户信息
    @Autowired
    private UserDetailService userDetailService;
    //手机短信登陆安全config
    @Autowired
    private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;

    /**
    * User (作者): MRui
    * TODO (描述)：security对密码需要进行加密（需要将PasswordEncoder类注册为bean才可生效）
     * security的加密方式（推荐，同一个密码加密后也不相同，在加密时会随机生成一个salt然后拿它跟密码进行hash保证结果一样）
    * Data：2019-01-17 23:25
    */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
    * User (作者): MRui
    * TODO (描述)：登陆‘记住我’功能配置（读写数据库）
    * Data：2019-01-23 22:43
    */
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        //数据库读写
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        //启动的时候系统会创建一张表详情看源码（启动一次注释掉即可否则会报重复创建的错）
        tokenRepository.setCreateTableOnStartup(securityProperties.isRememberMeTableCreate());
        return tokenRepository;
    }

    /**
    * User (作者): MRui
    * TODO (描述)：请求拦截方法
    * Data：2019-01-13 14:44
    */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //验证码过滤器
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        validateCodeFilter.setFailureHandler(failureHandle);
        validateCodeFilter.setSecurityProperties(securityProperties);
        validateCodeFilter.afterPropertiesSet();


            //在UsernamePasswordAuthenticationFilter过滤器之前执行
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
            //请求做授权
            .authorizeRequests()
            //允许访问的url
            .antMatchers(
                    Constants.DEFAULT_UNAUTHENTICATION_URL,
                    Constants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*",
                    Constants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
                    securityProperties.getLoginPage()
                    ).permitAll()
            //身份验证anyRequest：任何请求
            .anyRequest()
            //需要身份认证验证
            .authenticated()
            //注销可以访问
        .and()
            .logout().permitAll()
            //表单登陆
        .and()
            .formLogin()
            //自定义登陆页（登陆的地址）
            .loginPage(Constants.DEFAULT_UNAUTHENTICATION_URL)
            //使用UsernamePasswordAuthenticationFilter来处理登陆提交请求（获取账号密码）
            .loginProcessingUrl(Constants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
            //调用自定义成功处理器
            .successHandler(successHandle)
            //调用失败自定义处理器
            .failureHandler(failureHandle)
        .and()
            //记住我功能配置
            .rememberMe()
            .tokenRepository(persistentTokenRepository())
            //过期秒数
            .tokenValiditySeconds(securityProperties.getRememberMeSeconds())
            //登陆
            .userDetailsService(userDetailService)
            //csrf认证(跨域攻击防护)
        .and()
            .csrf().disable()
            //将自定义手机验证登陆Config中的所有配置加载进来
        .apply(smsCodeAuthenticationSecurityConfig);
    }

    /**
    * User (作者): MRui
    * TODO (描述)：资源忽略
    * Data：2019-01-13 14:45
    */
    @Override
    public void configure(WebSecurity web) throws Exception{
        /*web.ignoring().antMatchers("/js/**","/css/**","/images/**");*/
        web.ignoring().antMatchers("/**/*.js", "/lang/*.json", "/**/*.css", "/**/*.js", "/**/*.map", "/**/*.html",
                "/**/*.png");
    }

    /**
    * User (作者): MRui
    * TODO (描述)：case场景验证(登陆)
    * Data：2019-01-13 15:05
    */
    /*@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //在内存里有一个ADMIN角色的admin用户登陆了系统
        //case1 登陆即可
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                                     .withUser("admin")
                                     .password(new BCryptPasswordEncoder().encode("123456"))
                                     .roles("ADMIN");
        //case1 角色认证
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("demo")
                .password(new BCryptPasswordEncoder().encode("demo"))
                .roles("USER");
    }*/
}
